// Agent.cpp

#include "StdAfx.h"

#include "Common/Archive/ArchiveExtractCallback.h"

#include "Agent.h"

#include <algorithm>

#include "Common/Defs.h"

namespace Common
{
namespace Agent
{
STDMETHODIMP CAgentFolder::GetAgentFolder(CAgentFolder **agentFolder)
{
  *agentFolder = this;
  return S_OK;
}

void CAgentFolder::LoadFolder(CProxyFolder *folder)
{
  size_t i;
  CProxyItem item;
  item.Folder = folder;
  for (i = 0; i < folder->Folders.size(); i++)
  {
	item.Index = i;
	_items.push_back(item);
	LoadFolder(folder->Folders[i].get());
  }
  size_t start = folder->Folders.size();
  for (i = 0; i < folder->Files.size(); i++)
  {
	item.Index = start + i;
	_items.push_back(item);
  }
}

STDMETHODIMP CAgentFolder::LoadItems()
{
  if (!_agentSpec->_archiveLink.IsOpen)
	return E_FAIL;
  _items.clear();
  if (_flatMode)
	LoadFolder(_proxyFolderItem);
  return S_OK;
}

STDMETHODIMP CAgentFolder::GetNumberOfItems(UINT32 *numItems)
{
  if (_flatMode)
	*numItems = _items.size();
  else
	*numItems = _proxyFolderItem->Folders.size() +_proxyFolderItem->Files.size();
  return S_OK;
}

CString CAgentFolder::GetName(UINT32 index) const
{
  UINT32 realIndex;
  const CProxyFolder * folder;
  if (_flatMode)
  {
	const CProxyItem &item = _items[index];
	folder = item.Folder;
	realIndex = item.Index;
  }
  else
  {
	folder = _proxyFolderItem;
	realIndex = index;
  }

  if (realIndex < (UINT32)folder->Folders.size())
	return folder->Folders[realIndex]->Name;
  return folder->Files[realIndex - folder->Folders.size()].Name;
}

CString CAgentFolder::GetPrefix(UINT32 index) const
{
  if (!_flatMode)
	return CString();
  const CProxyItem &item = _items[index];
  const CProxyFolder *folder = item.Folder;
  CString path;
  while (folder != _proxyFolderItem)
  {
	path = folder->Name + CString(CHAR_PATH_SEPARATOR) + path;
	folder = folder->Parent;
  }
  return path;
}

CString CAgentFolder::GetFullPathPrefixPlusPrefix(UINT32 index) const
{
  return _proxyFolderItem->GetFullPathPrefix() + GetPrefix(index);
}

void CAgentFolder::GetPrefixIfAny(UINT32 index, CComPropVariant &prop) const
{
  if (!_flatMode)
	return;
  prop = CComBSTR(GetPrefix(index));
}


STDMETHODIMP CAgentFolder::GetProperty(UINT32 itemIndex, PROPID propID, PROPVARIANT *value)
{
  CComPropVariant prop;
  const CProxyFolder * folder;
  UINT32 realIndex;
  if (_flatMode)
  {
	const CProxyItem &item = _items[itemIndex];
	folder = item.Folder;
	realIndex = item.Index;
  }
  else
  {
	folder = _proxyFolderItem;
	realIndex = itemIndex;
  }

  if (realIndex < (UINT32)folder->Folders.size())
  {
	const auto &item = folder->Folders[realIndex];
	if (!_flatMode && propID == kpidSize)
	  prop = item->Size;
	else if (!_flatMode && propID == kpidPackSize)
	  prop = item->PackSize;
	else
	switch(propID)
	{
	  case kpidIsDir:      prop = true; break;
	  case kpidNumSubDirs: prop = item->NumSubFolders; break;
	  case kpidNumSubFiles:   prop = item->NumSubFiles; break;
	  case kpidName:          prop = CComBSTR(item->Name); break;
	  case kpidCRC:
	  {
		if (item->IsLeaf)
		{
		  HRESULT hRes(_agentSpec->GetArchive()->GetProperty(item->Index, propID, value));
		  if(S_OK != hRes)
			return hRes;
		}
		if (item->CrcIsDefined && value->vt == VT_EMPTY)
		  prop = item->Crc;
		break;
	  }
	  case kpidPrefix: GetPrefixIfAny(itemIndex, prop); break;

	  default:
		if (item->IsLeaf)
		  return _agentSpec->GetArchive()->GetProperty(item->Index, propID, value);
	}
  }
  else
  {
	realIndex -= folder->Folders.size();
	const CProxyFile &item = folder->Files[realIndex];
	switch(propID)
	{
	  case kpidIsDir: prop = false; break;
	  case kpidName: prop = CComBSTR(item.Name); break;
	  case kpidPrefix: GetPrefixIfAny(itemIndex, prop); break;
	  default:
		return _agentSpec->GetArchive()->GetProperty(item.Index, propID, value);
	}
  }
  prop.Detach(value);
  return S_OK;
}

HRESULT CAgentFolder::BindToFolder(CProxyFolder *folder, IFolderFolder **resultFolder)
{
  CComPtr<IFolderFolder> parentFolder;
  if (folder->Parent != _proxyFolderItem)
  {
	HRESULT hRes(BindToFolder(folder->Parent, &parentFolder));
	if(S_OK != hRes)
		return hRes;
  }
  else
	parentFolder = this;
  CAgentFolder *folderSpec = new CComObject<CAgentFolder>;
  folderSpec->FinalConstruct();
  CComPtr<IFolderFolder> agentFolder = folderSpec;
  folderSpec->Init(_proxyArchive, folder, parentFolder, _agentSpec);
  *resultFolder = agentFolder.Detach();
  return S_OK;
}

STDMETHODIMP CAgentFolder::BindToFolder(UINT32 index, IFolderFolder **resultFolder)
{
  try
  {

	  CProxyFolder * folder;
	  UINT32 realIndex;
	  if (_flatMode)
	  {
		const CProxyItem &item = _items[index];
		folder = item.Folder;
		realIndex = item.Index;
	  }
	  else
	  {
		folder = _proxyFolderItem;
		realIndex = index;
	  }
	  if (realIndex >= (UINT32)folder->Folders.size())
		return E_INVALIDARG;
	  return BindToFolder(folder->Folders[realIndex].get(), resultFolder);
  } catch(...) 
  { return E_OUTOFMEMORY; }
}

STDMETHODIMP CAgentFolder::BindToFolder(const wchar_t *name, IFolderFolder **resultFolder)
{
	try
	{
		int index = _proxyFolderItem->FindDirSubItemIndex(name);
		if (index < 0)
			return E_INVALIDARG;
		return BindToFolder(index, resultFolder);
	} catch(...) 
	{ return E_OUTOFMEMORY; }
}

STDMETHODIMP CAgentFolder::BindToParentFolder(IFolderFolder **resultFolder)
{
	try
	{
		CComPtr<IFolderFolder> parentFolder = _parentFolder;
		*resultFolder = parentFolder.Detach();
		return S_OK;
	} catch(...) 
	{ return E_OUTOFMEMORY; }
}

STDMETHODIMP CAgentFolder::GetStream(UINT32 index, ISequentialInStream **stream)
{
  CComPtr<IInArchiveGetStream> getStream;
  _agentSpec->GetArchive()->QueryInterface(__uuidof(IInArchiveGetStream), (void **)&getStream);
  if (!getStream)
	return S_OK;

  const CProxyFolder * folder;
  UINT32 realIndex;
  if (_flatMode)
  {
	const CProxyItem &item = _items[index];
	folder = item.Folder;
	realIndex = item.Index;
  }
  else
  {
	folder = _proxyFolderItem;
	realIndex = index;
  }

  UINT32 indexInArchive;
  if (realIndex < (UINT32)folder->Folders.size())
  {
	const auto &item = folder->Folders[realIndex];
	if (!item->IsLeaf)
	  return S_OK;
	indexInArchive = item->Index;
  }
  else
	indexInArchive = folder->Files[realIndex - folder->Folders.size()].Index;
  return getStream->GetStream(indexInArchive, stream);
}

STATPROPSTG kProperties[] =
{
  { NULL, kpidNumSubDirs, VT_UI4},
  { NULL, kpidNumSubFiles, VT_UI4},
  { NULL, kpidPrefix, VT_BSTR}
};

static const UINT32 kNumProperties = sizeof(kProperties) / sizeof(kProperties[0]);

STDMETHODIMP CAgentFolder::GetNumberOfProperties(UINT32 *numProps)
{
  try
  {
	  HRESULT hRes(_agentSpec->GetArchive()->GetNumberOfProperties(numProps));
	  if(S_OK != hRes)
		return hRes;
	  *numProps += kNumProperties;
	  if (!_flatMode)
		(*numProps)--;
	  if (!_agentSpec->_proxyArchive->ThereIsPathProp)
		(*numProps)++;
	  return S_OK;
  } catch(...) 
  { return E_OUTOFMEMORY; }
}

STDMETHODIMP CAgentFolder::GetPropertyInfo(UINT32 index, BSTR *name, PROPID *propID, VARTYPE *varType)
{
  try
  {
	UINT32 numProps;
	_agentSpec->GetArchive()->GetNumberOfProperties(&numProps);
	if (!_agentSpec->_proxyArchive->ThereIsPathProp)
	{
		if (index == 0)
		{
		  *propID = kpidName;
		  *varType = VT_BSTR;
		  *name = 0;
		  return S_OK;
		}
		index--;
	}

	if (index < numProps)
	{
		HRESULT hRes(_agentSpec->GetArchive()->GetPropertyInfo(index, name, propID, varType));
		if(S_OK != hRes)
			return hRes;
		if (*propID == kpidPath)
			*propID = kpidName;
	}
	else
	{
		const STATPROPSTG &srcItem = kProperties[index - numProps];
		*propID = srcItem.propid;
		*varType = srcItem.vt;
		*name = 0;
	}
	return S_OK;
	} catch(...) 
	{ return E_OUTOFMEMORY; }
}

STATPROPSTG kFolderProps[] =
{
  { NULL, kpidSize, VT_UI8},
  { NULL, kpidPackSize, VT_UI8},
  { NULL, kpidNumSubDirs, VT_UI4},
  { NULL, kpidNumSubFiles, VT_UI4},
  { NULL, kpidCRC, VT_UI4}
};

static const UINT32 kNumFolderProps = sizeof(kFolderProps) / sizeof(kFolderProps[0]);

STDMETHODIMP CAgentFolder::GetFolderProperty(PROPID propID, PROPVARIANT *value)
{
  try
  {
	CComPropVariant prop;
	  switch(propID)
	  {
		case kpidSize:         prop = _proxyFolderItem->Size; break;
		case kpidPackSize:     prop = _proxyFolderItem->PackSize; break;
		case kpidNumSubDirs:   prop = _proxyFolderItem->NumSubFolders; break;
		case kpidNumSubFiles:  prop = _proxyFolderItem->NumSubFiles; break;
		case kpidName:         prop = CComBSTR(_proxyFolderItem->Name); break;
		case kpidPath:         prop = CComBSTR(_proxyFolderItem->GetFullPathPrefix()); break;
		case kpidType: prop = CComBSTR(_T("7-Zip.")) + _agentSpec->ArchiveType; break;
		case kpidCRC: if (_proxyFolderItem->CrcIsDefined) prop = _proxyFolderItem->Crc; break;
	  }
	  prop.Detach(value);
	  return S_OK;
  } catch(...) 
  { return E_OUTOFMEMORY; }
}

STDMETHODIMP CAgentFolder::GetNumberOfFolderProperties(UINT32 *numProps)
{
  *numProps = kNumFolderProps;
  return S_OK;
}

STDMETHODIMP CAgentFolder::GetFolderPropertyInfo(UINT32 index, BSTR *name, PROPID *propID, VARTYPE *varType)
{
	const STATPROPSTG &srcItem = kFolderProps[index];
	*propID = srcItem.propid;
	*varType = srcItem.vt;
	*name = 0;
	return S_OK;
}

STDMETHODIMP CAgentFolder::GetFolderArcProps(IFolderArcProps **object)
{
  CComPtr<IFolderArcProps> temp = _agentSpec;
  *object = temp.Detach();
  return S_OK;
}

#ifdef NEW_FOLDER_INTERFACE

STDMETHODIMP CAgentFolder::SetFlatMode(INT32 flatMode)
{
  _flatMode = IntToBool(flatMode);
  return S_OK;
}

#endif

void CAgentFolder::GetRealIndices(const UINT32 *indices, UINT32 numItems, std::vector<UINT32> &realIndices) const
{
  if (!_flatMode)
  {
	_proxyFolderItem->GetRealIndices(indices, numItems, realIndices);
	return;
  }
  realIndices.clear();
  for(UINT32 i = 0; i < numItems; i++)
  {
	const CProxyItem &item = _items[indices[i]];
	const auto &folder = item.Folder;
	UINT32 realIndex = item.Index;
	if (realIndex < (UINT32)folder->Folders.size())
	  continue;
	realIndices.push_back(folder->Files[realIndex - folder->Folders.size()].Index);
  }
  std::sort(realIndices.begin(), realIndices.end());
}

STDMETHODIMP CAgentFolder::Extract(const UINT32 *indices,
	UINT32 numItems,
	Archive::Extract::PathMode pathMode,
	Archive::Extract::OverwriteMode overwriteMode,
	const wchar_t *path,
	INT32 testMode,
	IFolderArchiveExtractCallback *extractCallback2)
{
  try
  {
	  Common::Archive::CArchiveExtractCallback *extractCallbackSpec = new CComObject<Common::Archive::CArchiveExtractCallback>;
	  extractCallbackSpec->FinalConstruct();
	  CComPtr<IArchiveExtractCallback> extractCallback = extractCallbackSpec;
	  CStringVector pathParts;
	  CProxyFolder *currentProxyFolder = _proxyFolderItem;
	  while (currentProxyFolder->Parent)
	  {
		  pathParts.push_front(currentProxyFolder->Name);
		currentProxyFolder = currentProxyFolder->Parent;
	  }
  
	  /*
	  if (_flatMode)
		pathMode = Archive::Extract::PathMode::kNoPathnames;
	  */

	  extractCallbackSpec->InitForMulti(false, pathMode, overwriteMode);

	  extractCallbackSpec->Init(NULL, _agentSpec->GetArc(),
		  extractCallback2,
		  false, testMode ? true : false, false,
		  (path ? path : _T("")),
		  pathParts,
		  (UINT64)(INT64)-1);
	  std::vector<UINT32> realIndices;
	  GetRealIndices(indices, numItems, realIndices);
	  return _agentSpec->GetArchive()->Extract(&realIndices.front(),
		  realIndices.size(), testMode, extractCallback);
  } catch(...) 
  { return E_OUTOFMEMORY; }
}

/////////////////////////////////////////
// CAgent

CAgent::CAgent():
  _proxyArchive(NULL),
  _codecs(0)
{
}

void CAgent::FinalRelease()
{
  if (_proxyArchive != NULL)
	delete _proxyArchive;
}

STDMETHODIMP CAgent::Open(
	IInStream *inStream,
	const wchar_t *filePath,
	const wchar_t *arcFormat,
	BSTR *archiveType,
	IArchiveOpenCallback *openArchiveCallback)
{
  try
  {
	  _archiveFilePath = filePath;
	  FileSystem::File::CFileInfo fi;
	  if (!inStream)
	  {
		if (!fi.Find(_archiveFilePath))
		  return ::GetLastError();
		if (fi.IsDir())
		  return E_FAIL;
	  }
	  Common::Archive::CArcInfoEx archiverInfo0, archiverInfo1;

	  _compressCodecsInfo.Release();
	  _codecs = new CComObject<Common::Archive::CCodecs>;
	  _codecs->FinalConstruct();
	  _compressCodecsInfo = _codecs;
	  HRESULT hRes(_codecs->Load());
	  if(S_OK != hRes)
			return hRes;

	  std::vector<int> formatIndices;
	  if (!_codecs->FindFormatForArchiveType(arcFormat, formatIndices))
		return S_FALSE;

	  hRes = _archiveLink.Open(_codecs, formatIndices, false, inStream, _archiveFilePath, openArchiveCallback);
	  if(S_OK != hRes)
		return hRes;

	  Common::Archive::CArc * pArc = _archiveLink.Arcs.back().get();
	  if (!inStream)
	  {
		pArc->MTimeDefined = !fi.IsDevice;
		pArc->MTime = fi.ModificationTime;
	  }

	  ArchiveType = _codecs->Formats[pArc->FormatIndex].Name;
	  if (archiveType == 0)
		return S_OK;
	  return StringToBstr(ArchiveType, archiveType);
  } catch(...) 
  { return E_OUTOFMEMORY; }
}

STDMETHODIMP CAgent::ReOpen(IArchiveOpenCallback *openArchiveCallback)
{
  try
  {
	  if (_proxyArchive != NULL)
	  {
		delete _proxyArchive;
		_proxyArchive = NULL;
	  }
	  HRESULT hRes(_archiveLink.ReOpen(_codecs, _archiveFilePath, openArchiveCallback));
	  if(S_OK != hRes)
		return hRes;
	  return ReadItems();
  } catch(...) 
  { return E_OUTOFMEMORY; }
}

STDMETHODIMP CAgent::Close()
{
  try
  {
	return _archiveLink.Close();
  } catch(...) 
  { return E_OUTOFMEMORY; }
}

/*
STDMETHODIMP CAgent::EnumProperties(IEnumSTATPROPSTG **EnumProperties)
{
  return _archive->EnumProperties(EnumProperties);
}
*/

HRESULT CAgent::ReadItems()
{
  if (_proxyArchive != NULL)
	return S_OK;
  _proxyArchive = new CProxyArchive();
  return _proxyArchive->Load(GetArc(), NULL);
}

STDMETHODIMP CAgent::BindToRootFolder(IFolderFolder **resultFolder)
{
  try
  {
	  HRESULT hRes(ReadItems());
	  if(S_OK != hRes)
			return hRes;
	  CAgentFolder *folderSpec = new CComObject<CAgentFolder>;
	  folderSpec->FinalConstruct();
	  CComPtr<IFolderFolder> rootFolder = folderSpec;
	  folderSpec->Init(_proxyArchive, _proxyArchive->RootFolder.get(), NULL, this);
	  *resultFolder = rootFolder.Detach();
	  return S_OK;
  } catch(...) 
  { return E_OUTOFMEMORY; }
}


STDMETHODIMP CAgent::Extract(
	Archive::Extract::PathMode pathMode,
	Archive::Extract::OverwriteMode overwriteMode,
	const wchar_t *path,
	INT32 testMode,
	IFolderArchiveExtractCallback *extractCallback2)
{
  try
  {
	  Common::Archive::CArchiveExtractCallback *extractCallbackSpec = new CComObject<Common::Archive::CArchiveExtractCallback>;
	  extractCallbackSpec->FinalConstruct();
	  CComPtr<IArchiveExtractCallback> extractCallback = extractCallbackSpec;
	  extractCallbackSpec->InitForMulti(false, pathMode, overwriteMode);
	  extractCallbackSpec->Init(NULL, GetArc(),
		  extractCallback2,
		  false, testMode ? true : false, false,
		  path,
		  CStringVector(),
		  (UINT64)(INT64)-1);
	  return GetArchive()->Extract(0, (UINT32)(INT32)-1, testMode, extractCallback);
  } catch(...) 
  { return E_OUTOFMEMORY; }
}

STDMETHODIMP CAgent::GetNumberOfProperties(UINT32 *numProps)
{
  try
  {
	return GetArchive()->GetNumberOfProperties(numProps);
  } catch(...) 
  { return E_OUTOFMEMORY; }
}

STDMETHODIMP CAgent::GetPropertyInfo(UINT32 index,
	  BSTR *name, PROPID *propID, VARTYPE *varType)
{
  try
  {
	  HRESULT hRes(GetArchive()->GetPropertyInfo(index, name, propID, varType));
	  if(S_OK != hRes)
			return hRes;
	  if (*propID == kpidPath)
		*propID = kpidName;
	  return S_OK;
  } catch(...) 
  { return E_OUTOFMEMORY; }
}

STDMETHODIMP CAgent::GetArcNumLevels(UINT32 *numLevels)
{
  *numLevels = _archiveLink.Arcs.size();
  return S_OK;
}

STDMETHODIMP CAgent::GetArcProp(UINT32 level, PROPID propID, PROPVARIANT *value)
{
  try
  {
	  CComPropVariant prop;
	  Common::Archive::CArc * pArc = _archiveLink.Arcs[level].get();
	  switch(propID)
	  {
		case kpidType: prop = CComBSTR(GetTypeOfArc(pArc)); break;
		case kpidPath: prop = CComBSTR(pArc->Path); break;
		default: return pArc->Archive->GetArchiveProperty(propID, value);
	  }
	  prop.Detach(value);
	  return S_OK;
  } catch(...) 
  { return E_OUTOFMEMORY; }
}

STDMETHODIMP CAgent::GetArcNumProps(UINT32 level, UINT32 *numProps)
{
  return _archiveLink.Arcs[level]->Archive->GetNumberOfArchiveProperties(numProps);
}

STDMETHODIMP CAgent::GetArcPropInfo(UINT32 level, UINT32 index, BSTR *name, PROPID *propID, VARTYPE *varType)
{
  return _archiveLink.Arcs[level]->Archive->GetArchivePropertyInfo(index, name, propID, varType);
}

// MainItemProperty
STDMETHODIMP CAgent::GetArcProp2(UINT32 level, PROPID propID, PROPVARIANT *value)
{
  return _archiveLink.Arcs[level - 1]->Archive->GetProperty(_archiveLink.Arcs[level]->SubfileIndex, propID, value);
}

STDMETHODIMP CAgent::GetArcNumProps2(UINT32 level, UINT32 *numProps)
{
  return _archiveLink.Arcs[level - 1]->Archive->GetNumberOfProperties(numProps);
}

STDMETHODIMP CAgent::GetArcPropInfo2(UINT32 level, UINT32 index, BSTR *name, PROPID *propID, VARTYPE *varType)
{
  return _archiveLink.Arcs[level - 1]->Archive->GetPropertyInfo(index, name, propID, varType);
}

}}